package eval

import (
	"showen/lex"
	"showen/obj"
	"showen/parse"
	"testing"
)

func TestTailRecursionIntegration(t *testing.T) {
	env := obj.NewEnvironment()
	RegisterBuiltinProcedures(env)

	tests := []struct {
		name     string
		input    string
		expected string
	}{
		{
			name:     "factorial with tail recursion optimization",
			input:    "(define (factorial n acc) (if (= n 0) acc (factorial (- n 1) (* n acc)))) (factorial 10 1)",
			expected: "3628800",
		},
		{
			name:     "tail recursive sum of integers",
			input:    "(define (sum-integers n acc) (if (= n 0) acc (sum-integers (- n 1) (+ n acc)))) (sum-integers 100 0)",
			expected: "5050",
		},
		{
			name:     "tail recursive fibonacci",
			input:    "(define (fib n a b) (if (= n 0) a (fib (- n 1) b (+ a b)))) (fib 20 0 1)",
			expected: "6765",
		},
		{
			name:     "mutual tail recursion - is-even?",
			input:    "(define (is-even? n) (if (= n 0) #t (is-odd? (- n 1)))) (define (is-odd? n) (if (= n 0) #f (is-even? (- n 1)))) (is-even? 1000)",
			expected: "#t",
		},
		{
			name:     "tail recursive countdown",
			input:    "(define (countdown n) (if (= n 0) 0 (countdown (- n 1)))) (countdown 10000)",
			expected: "0",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			lexer := lex.NewLexer(tt.input)
			tokens := lexer.ReadTokens()
			parser := parse.NewParser(tokens)
			exprs, err := parser.ParseProgram()
			if err != nil {
				t.Fatalf("parser.ParseProgram() error = %v", err)
			}

			var result obj.Object
			for _, expr := range exprs {
				result, err = EvalExpressionTrampoline(expr, env)
				if err != nil {
					t.Fatalf("EvalExpressionTrampoline() error = %v", err)
				}
			}

			if result.Inspect() != tt.expected {
				t.Errorf("EvalExpressionTrampoline() = %v, want %v", result.Inspect(), tt.expected)
			}
		})
	}
}

func TestTailCallInNestedExpressions(t *testing.T) {
	env := obj.NewEnvironment()
	RegisterBuiltinProcedures(env)

	tests := []struct {
		name     string
		input    string
		expected string
	}{
		{
			name:     "tail call in nested let expressions",
			input:    "(let ((x 5)) (letrec ((helper (lambda (n) (if (= n 0) x (helper (- n 1)))))) (helper 1000)))",
			expected: "5",
		},
		{
			name:     "tail call in nested if expressions",
			input:    "(letrec ((test (lambda (n) (if (> n 0) (if (= n 1) 42 (test (- n 1))) 0)))) (test 1000))",
			expected: "42",
		},
		{
			name:     "tail call in nested begin expressions",
			input:    "(letrec ((loop (lambda (n) (begin (display \"\") (if (= n 0) n (loop (- n 1))))))) (loop 1000))",
			expected: "0",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			lexer := lex.NewLexer(tt.input)
			tokens := lexer.ReadTokens()
			parser := parse.NewParser(tokens)
			expr, err := parser.ParseExpression()
			if err != nil {
				t.Fatalf("parser.ParseExpression() error = %v", err)
			}

			result, err := EvalExpressionTrampoline(expr, env)
			if err != nil {
				t.Fatalf("EvalExpressionTrampoline() error = %v", err)
			}

			if result.Inspect() != tt.expected {
				t.Errorf("EvalExpressionTrampoline() = %v, want %v", result.Inspect(), tt.expected)
			}
		})
	}
}

func TestDeepTailRecursion(t *testing.T) {
	env := obj.NewEnvironment()
	RegisterBuiltinProcedures(env)

	// This test ensures that deep tail recursion doesn't cause stack overflow
	input := "(letrec ((deep-recursion (lambda (n) (if (= n 0) 0 (deep-recursion (- n 1)))))) (deep-recursion 50000))"

	lexer := lex.NewLexer(input)
	tokens := lexer.ReadTokens()
	parser := parse.NewParser(tokens)
	expr, err := parser.ParseExpression()
	if err != nil {
		t.Fatalf("parser.ParseExpression() error = %v", err)
	}

	result, err := EvalExpressionTrampoline(expr, env)
	if err != nil {
		t.Fatalf("EvalExpressionTrampoline() error = %v", err)
	}

	expected := "0"
	if result.Inspect() != expected {
		t.Errorf("EvalExpressionTrampoline() = %v, want %v", result.Inspect(), expected)
	}
}